{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42af78d9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Execute this cell to install dependencies\n",
    "%pip install sf-hamilton[visualization]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c98f181",
   "metadata": {},
   "source": [
    "# Static validator dev [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dagworks-inc/hamilton/blob/main/examples/validation/static_validator/notebook.ipynb) [![GitHub badge](https://img.shields.io/badge/github-view_source-2b3137?logo=github)](https://github.com/apache/hamilton/blob/main/examples/validation/static_validator/notebook.ipynb)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f35e3e0b",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-13T16:17:52.022254Z",
     "start_time": "2024-08-13T16:17:51.439954Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 12.0.0 (20240704.0754)\n",
       " -->\n",
       "<!-- Pages: 1 -->\n",
       "<svg width=\"109pt\" height=\"165pt\"\n",
       " viewBox=\"0.00 0.00 108.85 164.80\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 160.8)\">\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-160.8 104.85,-160.8 104.85,4 -4,4\"/>\n",
       "<g id=\"clust1\" class=\"cluster\">\n",
       "<title>cluster__legend</title>\n",
       "<polygon fill=\"#ffffff\" stroke=\"black\" points=\"8,-73.8 8,-148.8 92.85,-148.8 92.85,-73.8 8,-73.8\"/>\n",
       "<text text-anchor=\"middle\" x=\"50.42\" y=\"-131.5\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">Legend</text>\n",
       "</g>\n",
       "<!-- foo -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>foo</title>\n",
       "<path fill=\"#b4d8e4\" stroke=\"black\" d=\"M65.42,-63.6C65.42,-63.6 35.42,-63.6 35.42,-63.6 29.42,-63.6 23.42,-57.6 23.42,-51.6 23.42,-51.6 23.42,-12 23.42,-12 23.42,-6 29.42,0 35.42,0 35.42,0 65.42,0 65.42,0 71.42,0 77.42,-6 77.42,-12 77.42,-12 77.42,-51.6 77.42,-51.6 77.42,-57.6 71.42,-63.6 65.42,-63.6\"/>\n",
       "<text text-anchor=\"start\" x=\"39.92\" y=\"-40.5\" font-family=\"Helvetica,sans-Serif\" font-weight=\"bold\" font-size=\"14.00\">foo</text>\n",
       "<text text-anchor=\"start\" x=\"42.92\" y=\"-12.5\" font-family=\"Helvetica,sans-Serif\" font-style=\"italic\" font-size=\"14.00\">str</text>\n",
       "</g>\n",
       "<!-- function -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>function</title>\n",
       "<path fill=\"#b4d8e4\" stroke=\"black\" d=\"M72.85,-118.1C72.85,-118.1 28,-118.1 28,-118.1 22,-118.1 16,-112.1 16,-106.1 16,-106.1 16,-93.5 16,-93.5 16,-87.5 22,-81.5 28,-81.5 28,-81.5 72.85,-81.5 72.85,-81.5 78.85,-81.5 84.85,-87.5 84.85,-93.5 84.85,-93.5 84.85,-106.1 84.85,-106.1 84.85,-112.1 78.85,-118.1 72.85,-118.1\"/>\n",
       "<text text-anchor=\"middle\" x=\"50.42\" y=\"-94\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">function</text>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "<graphviz.graphs.Digraph at 0x14f34ebe0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%cell_to_module good_module --display\n",
    "from hamilton.function_modifiers import tag\n",
    "\n",
    "@tag(node_type=\"output\", table_name=\"my_table\")\n",
    "def foo() -> str:\n",
    "    return \"Hello, world!\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af8dc9b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext hamilton.plugins.jupyter_magic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "e8d725dc",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-13T16:18:06.041014Z",
     "start_time": "2024-08-13T16:18:05.676209Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 12.0.0 (20240704.0754)\n",
       " -->\n",
       "<!-- Pages: 1 -->\n",
       "<svg width=\"109pt\" height=\"165pt\"\n",
       " viewBox=\"0.00 0.00 108.85 164.80\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 160.8)\">\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-160.8 104.85,-160.8 104.85,4 -4,4\"/>\n",
       "<g id=\"clust1\" class=\"cluster\">\n",
       "<title>cluster__legend</title>\n",
       "<polygon fill=\"#ffffff\" stroke=\"black\" points=\"8,-73.8 8,-148.8 92.85,-148.8 92.85,-73.8 8,-73.8\"/>\n",
       "<text text-anchor=\"middle\" x=\"50.42\" y=\"-131.5\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">Legend</text>\n",
       "</g>\n",
       "<!-- foo -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>foo</title>\n",
       "<path fill=\"#b4d8e4\" stroke=\"black\" d=\"M65.42,-63.6C65.42,-63.6 35.42,-63.6 35.42,-63.6 29.42,-63.6 23.42,-57.6 23.42,-51.6 23.42,-51.6 23.42,-12 23.42,-12 23.42,-6 29.42,0 35.42,0 35.42,0 65.42,0 65.42,0 71.42,0 77.42,-6 77.42,-12 77.42,-12 77.42,-51.6 77.42,-51.6 77.42,-57.6 71.42,-63.6 65.42,-63.6\"/>\n",
       "<text text-anchor=\"start\" x=\"39.92\" y=\"-40.5\" font-family=\"Helvetica,sans-Serif\" font-weight=\"bold\" font-size=\"14.00\">foo</text>\n",
       "<text text-anchor=\"start\" x=\"42.92\" y=\"-12.5\" font-family=\"Helvetica,sans-Serif\" font-style=\"italic\" font-size=\"14.00\">str</text>\n",
       "</g>\n",
       "<!-- function -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>function</title>\n",
       "<path fill=\"#b4d8e4\" stroke=\"black\" d=\"M72.85,-118.1C72.85,-118.1 28,-118.1 28,-118.1 22,-118.1 16,-112.1 16,-106.1 16,-106.1 16,-93.5 16,-93.5 16,-87.5 22,-81.5 28,-81.5 28,-81.5 72.85,-81.5 72.85,-81.5 78.85,-81.5 84.85,-87.5 84.85,-93.5 84.85,-93.5 84.85,-106.1 84.85,-106.1 84.85,-112.1 78.85,-118.1 72.85,-118.1\"/>\n",
       "<text text-anchor=\"middle\" x=\"50.42\" y=\"-94\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">function</text>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "<graphviz.graphs.Digraph at 0x14f359790>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%cell_to_module bad_module --display\n",
    "from hamilton.function_modifiers import tag\n",
    "\n",
    "@tag(node_type=\"output\")\n",
    "def foo() -> str:\n",
    "    return \"Hello, world!\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "085fe856",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-13T16:19:09.270965Z",
     "start_time": "2024-08-13T16:19:09.266344Z"
    }
   },
   "outputs": [],
   "source": [
    "# Validator\n",
    "from typing import Optional\n",
    "from hamilton.lifecycle import api\n",
    "from hamilton.graph_types import HamiltonNode\n",
    "\n",
    "\n",
    "class MyTagValidator(api.StaticValidator):\n",
    "    \"\"\"Static validator that is run right after the graph is built.\"\"\"\n",
    "\n",
    "    def run_to_validate_node(\n",
    "            self, *, node: HamiltonNode, **future_kwargs\n",
    "    ) -> tuple[bool, Optional[str]]:\n",
    "        if node.tags.get(\"node_type\", \"\") == \"output\":\n",
    "            table_name = node.tags.get(\"table_name\")\n",
    "            if not table_name:  # None or empty\n",
    "                error_msg = f\"Node {node.tags['module']}.{node.name} is an output node, but does not have a table_name tag.\"\n",
    "                return False, error_msg\n",
    "        return True, None\n",
    "\n",
    "tag_validator = MyTagValidator()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c0ef5e03",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-13T16:19:09.998394Z",
     "start_time": "2024-08-13T16:19:09.975269Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'foo': 'Hello, world!'}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from hamilton import driver\n",
    "\n",
    "dr = (\n",
    "    driver.Builder()\n",
    "    .with_modules(good_module)\n",
    "    .with_adapters(tag_validator)\n",
    "    .build()\n",
    ")\n",
    "dr.execute([good_module.foo])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "6a452c8e",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-13T16:19:11.923694Z",
     "start_time": "2024-08-13T16:19:10.870198Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------------------\n",
      "Oh no an error! Need help with Hamilton?\n",
      "Join our slack and ask for help! https://join.slack.com/t/hamilton-opensource/shared_invite/zt-2niepkra8-DGKGf_tTYhXuJWBTXtIs4g\n",
      "-------------------------------------------------------------------\n",
      "\n"
     ]
    },
    {
     "ename": "ValidationException",
     "evalue": "Node validation failed! 1 errors encountered:\n  foo: Node bad_module.foo is an output node, but does not have a table_name tag.",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValidationException\u001b[0m                       Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[7], line 3\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# this should error\u001b[39;00m\n\u001b[1;32m      2\u001b[0m dr \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m----> 3\u001b[0m     \u001b[43mdriver\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mBuilder\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m      4\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mwith_modules\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbad_module\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m      5\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mwith_adapters\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtag_validator\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m      6\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbuild\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m      7\u001b[0m )\n",
      "File \u001b[0;32m~/dagworks/hamilton/hamilton/driver.py:1872\u001b[0m, in \u001b[0;36mBuilder.build\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m   1865\u001b[0m     grouping_strategy \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgrouping_strategy \u001b[38;5;129;01mor\u001b[39;00m grouping\u001b[38;5;241m.\u001b[39mGroupByRepeatableBlocks()\n\u001b[1;32m   1866\u001b[0m     graph_executor \u001b[38;5;241m=\u001b[39m TaskBasedGraphExecutor(\n\u001b[1;32m   1867\u001b[0m         execution_manager\u001b[38;5;241m=\u001b[39mexecution_manager,\n\u001b[1;32m   1868\u001b[0m         grouping_strategy\u001b[38;5;241m=\u001b[39mgrouping_strategy,\n\u001b[1;32m   1869\u001b[0m         adapter\u001b[38;5;241m=\u001b[39mlifecycle_base\u001b[38;5;241m.\u001b[39mLifecycleAdapterSet(\u001b[38;5;241m*\u001b[39madapter),\n\u001b[1;32m   1870\u001b[0m     )\n\u001b[0;32m-> 1872\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mDriver\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m   1873\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m   1874\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmodules\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m   1875\u001b[0m \u001b[43m    \u001b[49m\u001b[43madapter\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43madapter\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m   1876\u001b[0m \u001b[43m    \u001b[49m\u001b[43m_materializers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmaterializers\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m   1877\u001b[0m \u001b[43m    \u001b[49m\u001b[43m_graph_executor\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mgraph_executor\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m   1878\u001b[0m \u001b[43m    \u001b[49m\u001b[43m_use_legacy_adapter\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m   1879\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m~/dagworks/hamilton/hamilton/driver.py:412\u001b[0m, in \u001b[0;36mDriver.__init__\u001b[0;34m(self, config, adapter, _materializers, _graph_executor, _use_legacy_adapter, *modules)\u001b[0m\n\u001b[1;32m    410\u001b[0m     error \u001b[38;5;241m=\u001b[39m telemetry\u001b[38;5;241m.\u001b[39msanitize_error(\u001b[38;5;241m*\u001b[39msys\u001b[38;5;241m.\u001b[39mexc_info())\n\u001b[1;32m    411\u001b[0m     logger\u001b[38;5;241m.\u001b[39merror(SLACK_ERROR_MESSAGE)\n\u001b[0;32m--> 412\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[1;32m    413\u001b[0m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[1;32m    414\u001b[0m     \u001b[38;5;66;03m# TODO -- update this to use the lifecycle methods\u001b[39;00m\n\u001b[1;32m    415\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcapture_constructor_telemetry(error, modules, config, adapter)\n",
      "File \u001b[0;32m~/dagworks/hamilton/hamilton/driver.py:399\u001b[0m, in \u001b[0;36mDriver.__init__\u001b[0;34m(self, config, adapter, _materializers, _graph_executor, _use_legacy_adapter, *modules)\u001b[0m\n\u001b[1;32m    393\u001b[0m     materializer_factories, extractor_factories \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_process_materializers(\n\u001b[1;32m    394\u001b[0m         _materializers\n\u001b[1;32m    395\u001b[0m     )\n\u001b[1;32m    396\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgraph \u001b[38;5;241m=\u001b[39m materialization\u001b[38;5;241m.\u001b[39mmodify_graph(\n\u001b[1;32m    397\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgraph, materializer_factories, extractor_factories\n\u001b[1;32m    398\u001b[0m     )\n\u001b[0;32m--> 399\u001b[0m \u001b[43mDriver\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_perform_graph_validations\u001b[49m\u001b[43m(\u001b[49m\u001b[43madapter\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgraph\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgraph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgraph_modules\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodules\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    400\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m adapter\u001b[38;5;241m.\u001b[39mdoes_hook(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpost_graph_construct\u001b[39m\u001b[38;5;124m\"\u001b[39m, is_async\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m):\n\u001b[1;32m    401\u001b[0m     adapter\u001b[38;5;241m.\u001b[39mcall_all_lifecycle_hooks_sync(\n\u001b[1;32m    402\u001b[0m         \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpost_graph_construct\u001b[39m\u001b[38;5;124m\"\u001b[39m, graph\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgraph, modules\u001b[38;5;241m=\u001b[39mmodules, config\u001b[38;5;241m=\u001b[39mconfig\n\u001b[1;32m    403\u001b[0m     )\n",
      "File \u001b[0;32m~/dagworks/hamilton/hamilton/driver.py:339\u001b[0m, in \u001b[0;36mDriver._perform_graph_validations\u001b[0;34m(adapter, graph, graph_modules)\u001b[0m\n\u001b[1;32m    329\u001b[0m         errors \u001b[38;5;241m=\u001b[39m [\n\u001b[1;32m    330\u001b[0m             \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mnode_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00merror_delimiter\u001b[38;5;241m.\u001b[39mjoin([item\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mfor\u001b[39;00m\u001b[38;5;250m \u001b[39mitem\u001b[38;5;250m \u001b[39m\u001b[38;5;129;01min\u001b[39;00m\u001b[38;5;250m \u001b[39mmessages])\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m    331\u001b[0m             \u001b[38;5;28;01mfor\u001b[39;00m node_name, messages \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28msorted\u001b[39m(\n\u001b[1;32m    332\u001b[0m                 node_validation_results\u001b[38;5;241m.\u001b[39mitems(), key\u001b[38;5;241m=\u001b[39moperator\u001b[38;5;241m.\u001b[39mitemgetter(\u001b[38;5;241m0\u001b[39m)\n\u001b[1;32m    333\u001b[0m             )\n\u001b[1;32m    334\u001b[0m         ]\n\u001b[1;32m    335\u001b[0m         error_str \u001b[38;5;241m=\u001b[39m (\n\u001b[1;32m    336\u001b[0m             \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNode validation failed! \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(errors)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m errors encountered:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m  \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m    337\u001b[0m             \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m  \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mjoin(errors)\n\u001b[1;32m    338\u001b[0m         )\n\u001b[0;32m--> 339\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m lifecycle_base\u001b[38;5;241m.\u001b[39mValidationException(error_str)\n\u001b[1;32m    340\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m adapter\u001b[38;5;241m.\u001b[39mdoes_validation(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mvalidate_graph\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[1;32m    341\u001b[0m     validation_results \u001b[38;5;241m=\u001b[39m adapter\u001b[38;5;241m.\u001b[39mcall_all_validators_sync(\n\u001b[1;32m    342\u001b[0m         \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mvalidate_graph\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m    343\u001b[0m         output_only_failures\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m,\n\u001b[0;32m   (...)\u001b[0m\n\u001b[1;32m    346\u001b[0m         config\u001b[38;5;241m=\u001b[39mgraph\u001b[38;5;241m.\u001b[39mconfig,\n\u001b[1;32m    347\u001b[0m     )\n",
      "\u001b[0;31mValidationException\u001b[0m: Node validation failed! 1 errors encountered:\n  foo: Node bad_module.foo is an output node, but does not have a table_name tag."
     ]
    }
   ],
   "source": [
    "# this should error\n",
    "dr = (\n",
    "    driver.Builder()\n",
    "    .with_modules(bad_module)\n",
    "    .with_adapters(tag_validator)\n",
    "    .build()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0f31407f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
